//
//  main.m
//  KVOTest
//
//  Created by yao zhaofeng on 2018/4/3.
//  Copyright © 2018年 YaoZhaofeng. All rights reserved.
//
/*
 *参考链接：https://blog.csdn.net/wildfireli/article/details/18399645
 */

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
//#import <objc/objc.h>
#import <objc/message.h>

@interface XYKVOObject: NSObject

@property (nonatomic, assign) int x;
@property (nonatomic, assign) int y;
@property (nonatomic, assign) int z;
@property (nonatomic, assign) int age;

@end

@implementation XYKVOObject

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"keyPath is %@; chage's new value is %@,  old value is %@ ", keyPath, change[NSKeyValueChangeNewKey], change[NSKeyValueChangeOldKey]);
}

@end

@interface XYKVOObject (category)

@end

@implementation XYKVOObject (category)
-(void)XY_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
    //动态添加一个类
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [@"XYKVO_" stringByAppendingString:oldClassName];
    Class myClass = objc_allocateClassPair([self class], [newClassName UTF8String], 0);
    
    /*添加setter方法，相当于重写setter方法， "v@:i" 含义 @: id   : SEL     v : void   (苹果类型编码：https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
    OC(消息发送机制),方法由两部分组成，方法编号@selector和方法实现(imp方法指针)，先找方法编号再得到方法的指针，再执行方法的代码块。
     */
    class_addMethod(myClass, @selector(setAge:), (IMP)setAge, "v@:i");
    
    //注册新添加的这个类
    objc_registerClassPair(myClass);
    
    //修改被观察这的isa指针，isa指针指向Person类改成指向myclass这个类
    object_setClass(self, myClass);
    
    //将观察者的属性保存到当前类里面去
    objc_setAssociatedObject(self, "objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//    objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

//相当于重写父类的方法
void setAge(id self, SEL _cmd, int age) {
    
    //保存当前类
    Class myclass = [self class];
    int oldAge = [self age];
    //将self的isa指针指向父类
    object_setClass(self, class_getSuperclass([self class]));
    
    //调用父类
    ((id(*)(id, SEL, int))objc_msgSend)(self, _cmd, age);
//    objc_msgSend(self, _cmd, age);
//    objc_msgSend(self, @selector(setAge:),age);
    
    //拿出观察者
    id objc = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
    
    //通知观察者
    NSString *keyPath = [NSString stringWithFormat:@"%d", age];
    NSDictionary *changeDic = @{NSKeyValueChangeNewKey: @(age), NSKeyValueChangeOldKey: @(oldAge)};
    ((id(*)(id, SEL, id, id, id, void*))objc_msgSend)(objc, @selector(observeValueForKeyPath:ofObject:change:context:), self, keyPath, changeDic, nil);
    
    //改为子类
    object_setClass(self, myclass);
}
@end

static NSArray *getClassMethodNames(Class c)
{
    NSMutableArray *resultArr = [NSMutableArray array];
    
    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList(c, &methodCount);
    for (int i = 0; i < methodCount; i++) {
        [resultArr addObject:NSStringFromSelector(method_getName(methodList[i]))];
    }
    free(methodList);
    
    return resultArr;
}

//object_getClass和obj->isa是同一个东西？
//obj->isa报错，怎么访问isa指针？
static void LogDescription(NSString *name, id obj) {
    NSString *str = [NSString stringWithFormat:
                     @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
                     name,
                     obj,
                     class_getName([obj class]),
                     object_getClassName(obj),
                     [getClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
    NSLog(@"%@\n", str);
}

//static void PrintDescription(NSString *name, id obj)
//{
//    NSString *str = [NSString stringWithFormat:
//                     @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
//                     name,
//                     obj,
//                     class_getName([obj class]),
//                     class_getName(obj->isa()),
//                     [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]];
//    printf("%s\n", [str UTF8String]);
//}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        XYKVOObject *x = [[XYKVOObject alloc] init];
        XYKVOObject *y = [[XYKVOObject alloc] init];
        XYKVOObject *xy = [[XYKVOObject alloc] init];
        XYKVOObject *control = [[XYKVOObject alloc] init];
        
        [x XY_addObserver:x forKeyPath:@"age" options:0 context:NULL];
        [xy addObserver:xy forKeyPath:@"x" options:0 context:NULL];
        [y addObserver:y forKeyPath:@"y" options:0 context:NULL];
        [xy addObserver:xy forKeyPath:@"y" options:0 context:NULL];
        
        [x setAge:100];
        
        //-class都一样
        //object_getClassName显示，被观察的对象所属类变成了NSKVONotifying_XYKVOObject，他就是动态生成的XYKVOObject的子类
        //对于被观察不同属性的不同实例，只生成了一个子类，将不同实例中被观察的不同属性的set方法都重写了，没有观察的（setZ）没有被重写，
        //从方法列表还可以看出，还重写了-class, dealloc, _isKVOA
        LogDescription(@"control", control);
        LogDescription(@"x", x);
        LogDescription(@"y", y);
        LogDescription(@"xy", xy);
        
        //验证NSKVONotifying_XYKVOObject的父类为XYKVOObject
        NSLog(@"NSKVONotifying_XYKVOObject 父类：%s", class_getName(class_getSuperclass(object_getClass(x))));
        
        //NSMethodSignature指针和IMP是同一种东西？
        //被kvo观察后，setX方法指针发生了变化
        NSLog(@"Using NSObject methods, normal setX: is %p, overridden setX: is %p\n",
               [control methodForSelector:@selector(setX:)],
               [x methodForSelector:@selector(setX:)]);
        NSLog(@"Using libobjc functions, normal setX: is %p, overridden setX: is %p\n",
               method_getImplementation(class_getInstanceMethod(object_getClass(control),
                                                                @selector(setX:))),
               method_getImplementation(class_getInstanceMethod(object_getClass(x),
                                                                @selector(setX:))));
    }
    return 0;
}
